/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.andersen.database.dao;

import com.andersen.database.entity.*;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Component;

/**
 *
 * @author Corvus 11
 */

 @Component
 public class UserDao extends AbstractDao{
	
     /**
      * Список ролей с их id в базе. Загружается один раз для всех Dao этого типа
      */
     private static Map<String,Long> roleMap = null;
     
     @PostConstruct
     @Override
     public void initIt() {
         LOG.info("Инициализация UserDao");
         super.initIt();
         if (roleMap == null) loadRoleMap();
     }
     
     /**
      * Получить объект из ResultSet запроса
      * @param rset
      * @return
      * @throws SQLException 
      */
     private UserEntity mapResultSetRow(ResultSet rset) throws SQLException {
         UserEntity u = new UserEntity();
         u.setActive(rset.getBoolean("active"));
         u.setId(rset.getLong("id"));
         u.setLogin(rset.getString("login"));
         u.setPassword(rset.getString("password"));
         u.setEmployeeId(rset.getLong("employee_id"));
         return u;
     }
     
     private void loadRoleMap() {
         
         LOG.info("Загрузка списка ролей");
            
            Connection conn = null;
            PreparedStatement st = null;
            ResultSet rs = null;

         roleMap = new HashMap<String, Long>();
            
            try {
                conn = dataSource.getConnection();
                st = conn.prepareStatement("SELECT * FROM role;");
                rs = st.executeQuery();
                while (rs.next()) {
                    String role = rs.getString("role");
                    long id = rs.getLong("id");
                    LOG.info("Загружена роль " + role + " (id = " + id + ")");
                    roleMap.put(role, id); 
                }
                if (!roleMap.containsKey(Roles.ADMIN) || 
                    !roleMap.containsKey(Roles.HR) ||
                    !roleMap.containsKey(Roles.MANAGER) ||
                    !roleMap.containsKey(Roles.USER))
                    LOG.error("Error with UserDao.loadRoleMap() - some required roles don't exist in database");
            } catch(SQLException exc) {
                 LOG.error("Error with getting role id", exc);
            } finally {
                  safeClose(rs);
                  safeClose(st);
                  safeClose(conn);
            }
     }
     
     /**
      * Получить пользователя по логину
      * Метод не загружает список ролей
      * @param login
      * @return 
      */
     public UserEntity getUser(String login) {
		LOG.info("Загрузка User по значению login = " + login);
		
		UserEntity entity = null;
                
                Connection conn = null;
                PreparedStatement st = null;
                ResultSet rs = null;

                try {
                    conn = dataSource.getConnection();
                    st = conn.prepareStatement("SELECT * from user u WHERE u.login LIKE (?);");
                    st.setString(1, login);
                    rs = st.executeQuery();
                    if (rs.first()) {
                        entity = mapResultSetRow(rs);
                    }
                } catch(SQLException exc) {
                      LOG.error("Ошибка при получении объекта User", exc);
                    } finally {
                        safeClose(rs);
                        safeClose(st);
                        safeClose(conn);
                    }
                
		return entity;
	}
     
     /**
      * Получить пользователя по id
      * Метод не загружает список ролей
      * @param id
      * @return 
      */
     public UserEntity getUser(long id) {
		LOG.info("getting user " + id + " from db");
		
		UserEntity entity = null;
                ArrayList<String> roles = null;
                
                Connection conn = null;
                PreparedStatement st = null;
                ResultSet rs = null;

                try {
                    conn = dataSource.getConnection();
                    st = conn.prepareStatement("SELECT * from user u WHERE u.id=?;");
                    st.setLong(1, id);
                    rs = st.executeQuery();
                    if (rs.first()) {
                        entity = mapResultSetRow(rs);
                    }
                } catch(SQLException exc) {
                      LOG.error("Ошибка при получении объекта User", exc);
                    } finally {
                        safeClose(rs);
                        safeClose(st);
                        safeClose(conn);
                    }
                
		return entity;
	}
        
     /**
      * Получить список ролей пользователя с указанным id
      * @param userId
      * @return 
      */
        public List<String> getUserRoles(long userId) {
            LOG.info("Получение списка ролей для User с Id = " + userId);
            ArrayList<String> roles = new ArrayList<String>();
            
            Connection conn = null;
            PreparedStatement st = null;
            ResultSet rs = null;

            StringBuilder sql = new StringBuilder();
            sql.append("SELECT r.role ");
            sql.append("FROM user_role ur, role r ");
            sql.append("WHERE ur.user_id = ? AND (ur.role_id = r.id);");
            
            try {
                conn = dataSource.getConnection();
                st = conn.prepareStatement(sql.toString());
                st.setLong(1, userId);
                rs = st.executeQuery();
                
                while (rs.next()) {
                   roles.add(rs.getString(1));
                }
            } catch(SQLException exc) {
                      LOG.error("Ошибка при загрузке ролей", exc);
                    } finally {
                        safeClose(rs);
                        safeClose(st);
                        safeClose(conn);
                   }
        
            return roles;
        }
        
        /**
         * Вставка нового пользователя
         * Метод не добавляет роли пользователя
         * @param user
         * @return новый id для добавленного пользователя
         */
        public long insert(UserEntity user) {
            LOG.info("inserting new user");
        
            long newKey = ID_INVALID;

            Connection conn = null;
            PreparedStatement st = null;
            ResultSet rs = null;

            String sql = "INSERT INTO user(login,password,employee_id) VALUE(?,?,?);";
            
            try {
                conn = dataSource.getConnection();
                st = conn.prepareStatement(sql,PreparedStatement.RETURN_GENERATED_KEYS);
                st.setString(1, user.getLogin());
                st.setString(2, user.getPassword());
                st.setLong(3, user.getEmployeeId());
                if (st.executeUpdate() > 0) {
                    rs = st.getGeneratedKeys();
                    rs.first();
                    newKey = rs.getLong(1);
                }
                
            } catch(SQLException exc) {
                  LOG.error("Error with inserting user", exc);
                } finally {
                    safeClose(rs);
                    safeClose(st);
                    safeClose(conn);
                }
            return newKey;
        }

        /**
         * Обновить информацию о пользователе
         * Метод не обновляет роли
         * @param user объект с заданным id и login (хэш пароля обязателен только при его изменении)
         * @return true в случае успешного изменения
         */
        public boolean update(UserEntity user) {
                Connection conn = null;
                PreparedStatement st = null;

                boolean result = false;
                
                try {
                    conn = dataSource.getConnection();
                    String sql = "UPDATE user SET password=(?) WHERE id=(?);";
                    st = conn.prepareStatement(sql);
                    st.setString(1, user.getPassword());
                    st.setLong(2, user.getId());
                    result = (st.executeUpdate() > 0);
                } catch(SQLException exc) {
                      LOG.error("Error with UserDao.update()", exc);
                    } finally {
                        safeClose(st);
                        safeClose(conn);
                    }
                
		return result;            
        }
        
        /**
         * Получает список всех пользователей из БД (без их ролей)
         * @return 
         */
        public ArrayList<UserEntity> getAllObjects() {
            LOG.info("Получение списка всех пользователей");
		
		ArrayList<UserEntity> users = new ArrayList<UserEntity>();
                
                Connection conn = null;
                PreparedStatement st = null;
                ResultSet rs = null;

                try {
                    conn = dataSource.getConnection();
                    st = conn.prepareStatement("SELECT * from user;");
                    rs = st.executeQuery();
                    while (rs.next()) {
                       UserEntity entity = mapResultSetRow(rs);
                       users.add(entity);
                    }
                } catch(SQLException exc) {
                      LOG.error("Error with getting user", exc);
                    } finally {
                        safeClose(rs);
                        safeClose(st);
                        safeClose(conn);
                    }
                
		return users;
        }
        
        /**
         * Добавить список ролей пользователя в БД
         * @param userId id пользователя
         * @param roles список ролей
         * @return количество добавленных ролей
         */
        /*public int addRoles(long userId, List<String> roles) {
            int count = 0;
            for(String r : roles) {
                if (addRole(userId, r)) count++;
            }
            return count;
        }*/
        
        /**
         * Добавить роль в БД
         * @param userId id пользователя
         * @param roleName имя роли
         * @return true, если роль успешно добавлена
         */
        public boolean addRole(long userId, String roleName) {
            return editRole(userId,roleName,true);
        }
        
        /**
         * Удалить роль из БД
         * @param userId id пользователя
         * @param roleName имя роли
         * @return true, если роль была удалена
         */
        public boolean dropRole(long userId, String roleName) {
            return editRole(userId,roleName,false);
        }
        
        private boolean editRole(long userId, String roleName, boolean add) {
            Connection conn = null;
            PreparedStatement st = null;
            ResultSet rs = null;
            
            boolean result = false;
            
            try {
               conn = dataSource.getConnection();
               String sql = (add) ? "INSERT INTO user_role(user_id,role_id) VALUE(?,?);" :
                       "DELETE FROM user_role WHERE user_id=? AND role_id=?;";
               st = conn.prepareStatement(sql);
               st.setLong(1, userId);
               st.setLong(2, roleMap.get(roleName));
               result = (st.executeUpdate() > 0);
            } catch(SQLException exc) {
               if (add) 
                   LOG.error("Роль " + roleName + " не была добавлена пользователю с id=" + userId, exc);
               else
                   LOG.error("Роль " + roleName + " не была снята с пользователя с id=" + userId, exc);
               result = false;
            } finally {
                safeClose(rs);
                safeClose(st);
                safeClose(conn);
            }
            return result;
        }
        
        public ArrayList<UserEntity> queryUsers(int from_item, int count, int sort_by, int sort_order) {
            Connection conn = null;
            PreparedStatement st = null;
            ResultSet rs = null;
            
            ArrayList<UserEntity> list = new ArrayList<UserEntity>();
            
            try {
                StringBuilder sql = new StringBuilder();
                sql.append("SELECT u.id, ");
                    sql.append("u.login, ");
                    sql.append("u.employee_id, ");
                    sql.append("e.name, ");
                    sql.append("e.surname, ");
                    sql.append("e.patronymic, ");
                    sql.append("e.skype, ");
                    sql.append("e.phone, ");
                    sql.append("u.active ");
                sql.append("FROM user u, employee e ");
                sql.append("WHERE u.employee_id = e.id ");
                
                switch((sort_by * 2) + sort_order) {
                    case 0: sql.append("ORDER BY e.surname, e.name, e.patronymic "); break;
                    case 1: sql.append("ORDER BY e.surname DESC, e.name DESC, e.patronymic DESC "); break;
                    case 2: sql.append("ORDER BY u.login "); break;
                    case 3: sql.append("ORDER BY u.login DESC "); break;
                    default: throw new SQLException("Ошибка параметра в queryUsers(). Параметр равен " + (sort_by + sort_order) + "(допустимо от 0 до 4)");
                }
                
                appendPaginationParams(sql, from_item, count);
                
                sql.append(";");
                
                LOG.info("query: ");
                LOG.info(sql);
                conn = dataSource.getConnection();
                st = conn.prepareStatement(sql.toString());
                rs = st.executeQuery();
                while (rs.next()) {
                    UserEntity user = new UserEntity();
                    user.setId(rs.getLong(1));
                    user.setLogin(rs.getString(2));
                    user.setEmployeeId(rs.getLong(3));
                    user.setActive(rs.getBoolean(9));
                    EmployeeEntity empl = new EmployeeEntity();
                    empl.setId(rs.getLong(3));
                    empl.setName(rs.getString(4));
                    empl.setSurname(rs.getString(5));
                    empl.setPatronymic(rs.getString(6));
                    empl.setSkype(rs.getString(7));
                    empl.setPhone(rs.getString(8));
                    user.setEmployee(empl);
                    list.add(user);
                }
        } catch (SQLException exc) {
            LOG.error("Ошибка при выполнении queryUsers",exc);
            return null;
        } finally {
           safeClose(rs);
           safeClose(st);
           safeClose(conn);
        }
        return list;
    }
        
        public long getUserCount() {
            Connection conn = null;
            PreparedStatement st = null;
            ResultSet rs = null;
            
            long cnt = -1;
            
            try {
               conn = dataSource.getConnection();
               st = conn.prepareStatement("SELECT COUNT(id) FROM user;");
               rs = st.executeQuery();
               if (rs.first())
                   cnt = rs.getLong(1) - 1;
            }catch(SQLException exc) {
               LOG.error("Ошибка подсчета размера таблицы",exc);
               cnt = -1;
            } finally {
                try {
                    if (rs != null) rs.close();
                    if (st != null) st.close();
                    if (conn!=null) conn.close();
                    } catch (SQLException exc3) {}
            }
            return cnt;
        }
        
        public boolean setUserActivity(long userId, boolean active) {
            Connection conn = null;
            PreparedStatement st = null;
            //ResultSet rs = null;
            
            boolean result = false;
            
            try {
               conn = dataSource.getConnection();
               st = conn.prepareStatement("UPDATE user SET active=(?) WHERE id=(?);");
               st.setBoolean(1,active);
               st.setLong(2,userId);
               result = (st.executeUpdate()>0);
               
            }catch(SQLException exc) {
               LOG.error("Ошибка блокировки/разблокировки пользователя",exc);
               result = false;
            } finally {
                try {
                    //if (rs != null) rs.close();
                    if (st != null) st.close();
                    if (conn!=null) conn.close();
                    } catch (SQLException exc3) {}
            }
            return result;
        }
        
        public UserEntity getUserByEmployeeId(long employeeId) {
            LOG.info("getting user by employee" + employeeId + " from db");
		
		UserEntity entity = null;
                
                Connection conn = null;
                PreparedStatement st = null;
                ResultSet rs = null;

                try {
                    conn = dataSource.getConnection();
                    st = conn.prepareStatement("SELECT * from `user` u WHERE u.`employee_id`=?;");
                    st.setLong(1, employeeId);
                    rs = st.executeQuery();
                    if (rs.first()) {
                        entity = mapResultSetRow(rs);
                    }
                } catch(SQLException exc) {
                      LOG.error("Ошибка при получении объекта User", exc);
                    } finally {
                        try {
                            if (rs != null) rs.close();
                            if (st != null) st.close();
                            if (conn!=null) conn.close();
                        } catch (SQLException e) {}
                    }
                
		return entity;
        }
}